
import java.util.ArrayList;
import java.util.Collections;
import java.util.Random;

/*
 * Engine for the genetic algorithm code
 */
public class GeneticEngine {
	
	public static int NUM_CHILDREN = 3;
	public static int NUM_WINNERS = 10;
	public static int NUM_RANDOM_HEURISTICS = 10;
	public static final int numberOfThreads = 3;
	private int village_id;
	
	public static Random random = new Random();
	
	public GeneticEngine(int village_id) {
		this.village_id = village_id;
	}


	/**
	 * Return a random float between a start and end range
	 */
	public float v( double start, double end ) {
		float range = (float)end - (float)start;
		
		return random.nextFloat()*range + (float)start;
	}
	
	
	/*
	 * Waits for a given arraylist of threads to complete, or does nothing if null is passed.
	 */
	private static void safe_join( ArrayList<MultiRun> threads ) {
		for( Thread thread : threads ) {
			if( thread != null ) {
				try {
					thread.join();
				} catch( InterruptedException e ) {
					System.err.println( "Could not join thread in safe_join: "  +e );
				}
			}
		}
	}
	
	/*
	 * Implement a thread class so that we can run games in different threads.
	 */
	private class MultiRun extends Thread {
		public MancalaPlayer player1;
		public MancalaPlayer player2;
		
		public void setPlayers( MancalaPlayer player1, MancalaPlayer player2 ) {
			this.player1 = player1;
			this.player2 = player2;
		}
		
		@Override
		public void run() {
			try {
				quickplay( player1, player2 );
			} catch ( Exception e ) {
				System.err.println( e );
			}
		}
	}
	
	public  ArrayList<group1_KalahHeuristic> run_game( ArrayList<group1_KalahHeuristic> current_generation ) {
		@SuppressWarnings("unchecked")
		ArrayList<group1_KalahHeuristic> heuristics = (ArrayList<group1_KalahHeuristic>) current_generation.clone();
		double start_time = System.currentTimeMillis();

		int game_count = play_heuristics( heuristics, heuristics, numberOfThreads );	
			
		// Retrieve the most winning heuristics 
		ArrayList<group1_KalahHeuristic> winners  = get_top_heuristics( heuristics, NUM_WINNERS );
		ArrayList<group1_KalahHeuristic> children = new ArrayList<group1_KalahHeuristic>();
		
		LearningEngine.setLongestHeuristicNameLength(winners);

		System.out.println( "This generations winners:" );
		
		for( int i = 0; i < winners.size(); i++ ) {
			
			group1_KalahHeuristic heuristic = winners.get(i);
			System.out.println( heuristic );
				
			// Create children for each heuristic and mutate them a little bit.
			for( int j = 1; j < 8; j++ ) { 
				heuristic.child++;
				group1_KalahHeuristic copy = heuristic.copy();
				mutate( copy, 0.85f, 1.15f, heuristic.child, j );
				children.add(copy);
			}
				
		}
			
		heuristics.clear();
		heuristics.addAll( winners );
		heuristics.addAll( children );
		heuristics.addAll( generate_random_heuristics( NUM_RANDOM_HEURISTICS) );
		
		double total_time = System.currentTimeMillis() - start_time;
		total_time /= 1000.0;
		
		System.out.println( String.format( "%d games finished in %.3f seconds.", game_count, total_time ) );
		
		return heuristics;
	}
	
	/*
	 * Plays a list of heuristics against another list of heuristics.
	 * It is safe to pass the same object into each array.
	 * Each heuristic in players1 will play against each heuristic in players2
	 * Returns the number of games that were played.
	 */
	private int play_heuristics( ArrayList<group1_KalahHeuristic> players1, ArrayList<group1_KalahHeuristic> players2, int threadcount ) {
		ArrayList<MultiRun> threads = new ArrayList<MultiRun>();
		int game_count = 0;
		
		for( int i = 0; i < players1.size(); i++ ) {
			for( int j = 0; j < players2.size(); j++ ) {
				group1_KalahHeuristic h1 = players1.get( i );
				group1_KalahHeuristic h2 = players2.get( j );
				
				// Skip over equivalent objects
				if( h1 == h2 )
					continue;
				game_count++;
				
				group1_Player player1 = new group1_Player( 0 );
				group1_Player player2 = new group1_Player( 1 );
				player1.setHeuristic(h1);
				player2.setHeuristic(h2);
				MultiRun thread = new MultiRun();
				
				thread.setPlayers( player1, player2 );
				threads.add(thread);
				
				thread.start();
				// If the number of current threads is equal to the maximum number of threads, wait all of them to finish
				if( threads.size() == threadcount ) {
					
					safe_join( threads );
					threads.clear();
				}
				
//				if ((game_count % 2460) == 0){
//					System.out.format("%d games completed taking a 10 second cooldown break\n", game_count); 
//					
//					try 
//					{
//						Thread.sleep(10000);
//					} 
//					catch (InterruptedException e) 
//					{
//						// TODO Auto-generated catch block
//						e.printStackTrace();
//					}
//				}
				
			}
		}
		
		safe_join( threads );
		
		return game_count;
	}
	
	/*
	 * Return a list of randomly generated heuristics
	 */
	public ArrayList<group1_KalahHeuristic> generate_random_heuristics(int count) {
		ArrayList<group1_KalahHeuristic> heuristics = new ArrayList<group1_KalahHeuristic>();
		int generation = GeneticVillageSimulator.iteration+1;
		
		for( int i = 0; i < count; i++ ) {
			heuristics.add( new group1_KalahHeuristic(
					v(-100, 100), v(-100,100), v(-100,100), v(-100,100), v(-100,100), v(-100,100), v(-100,100), v(-100,100), v(-100,100),
					v(-100, 100), v(-100,100), v(-100,100), v(-100,100), v(-100,100), v(-100,100), v(-100,100), v(-100,100), v(-100,100),
					v(-100, 100), v(-100,100), v(-100,100), v(-100,100), v(-100,100), v(-100,100), v(-100,100), v(-100,100), v(-100,100),
					"V"+village_id+"G"+generation+"R"+i ) );
		}
		return heuristics;
	}
	
	/*
	 * Mutate this heuristic by a certain range. Example: mutate( 0.5, 1.5 ) will pick a random number between 0.5 and 1.5
	 * for each weight, and then multiply the weight by that value.
	 */
	public group1_KalahHeuristic mutate( group1_KalahHeuristic heuristic, float lowerbound, float upperbound,int j, int mode ) {
		group1_KalahHeuristic copy = heuristic.copy();
		boolean should_mutate_1 = false;
		boolean should_mutate_2 = false;
		boolean should_mutate_3 = false;
		
		if( ( mode & 1 ) > 0 )
			should_mutate_1 = true;
		if( ( mode & 2 ) > 0 )
			should_mutate_2 = true;
		if( ( mode & 4 ) > 0 )
			should_mutate_3 = true;
		
		if( should_mutate_1 ) {
			heuristic.h1_1 = heuristic.h1_1 * v( lowerbound, upperbound );
			heuristic.h2_1 = heuristic.h2_1 * v( lowerbound, upperbound );
			heuristic.h3_1 = heuristic.h3_1 * v( lowerbound, upperbound );
			heuristic.h4_1 = heuristic.h4_1 * v( lowerbound, upperbound );
			heuristic.h5_1 = heuristic.h5_1 * v( lowerbound, upperbound );
			heuristic.h6_1 = heuristic.h6_1 * v( lowerbound, upperbound );
			heuristic.h7_1 = heuristic.h7_1 * v( lowerbound, upperbound );
			heuristic.h8_1 = heuristic.h8_1 * v( lowerbound, upperbound );
			heuristic.h9_1 = heuristic.h9_1 * v( lowerbound, upperbound );
		}
		
		if( should_mutate_2 ) {
			heuristic.h1_2 = heuristic.h1_2 * v( lowerbound, upperbound );
			heuristic.h2_2 = heuristic.h2_2 * v( lowerbound, upperbound );
			heuristic.h3_2 = heuristic.h3_2 * v( lowerbound, upperbound );
			heuristic.h4_2 = heuristic.h4_2 * v( lowerbound, upperbound );
			heuristic.h5_2 = heuristic.h5_2 * v( lowerbound, upperbound );
			heuristic.h6_2 = heuristic.h6_2 * v( lowerbound, upperbound );
			heuristic.h7_2 = heuristic.h7_2 * v( lowerbound, upperbound );
			heuristic.h8_2 = heuristic.h8_2 * v( lowerbound, upperbound );
			heuristic.h9_2 = heuristic.h9_2 * v( lowerbound, upperbound );
		}
		
		if( should_mutate_3 ) {
			heuristic.h1_3 = heuristic.h1_3 * v( lowerbound, upperbound );
			heuristic.h2_3 = heuristic.h2_3 * v( lowerbound, upperbound );
			heuristic.h3_3 = heuristic.h3_3 * v( lowerbound, upperbound );
			heuristic.h4_3 = heuristic.h4_3 * v( lowerbound, upperbound );
			heuristic.h5_3 = heuristic.h5_3 * v( lowerbound, upperbound );
			heuristic.h6_3 = heuristic.h6_3 * v( lowerbound, upperbound );
			heuristic.h7_3 = heuristic.h7_3 * v( lowerbound, upperbound );
			heuristic.h8_3 = heuristic.h8_3 * v( lowerbound, upperbound );
			heuristic.h9_3 = heuristic.h9_3 * v( lowerbound, upperbound );
		}
		
		heuristic.name = heuristic.name+"C"+j;
		
		return copy;
	}
	
	/*
	 * Sorts the heuristics and returns the highest scoring ones.
	 */
	public static ArrayList<group1_KalahHeuristic> get_top_heuristics( ArrayList<group1_KalahHeuristic> heuristics, int count ) {
		ArrayList<group1_KalahHeuristic> top_heuristics = new ArrayList<group1_KalahHeuristic>();
		group1_KalahHeuristic.KalahComparator comparator = new group1_KalahHeuristic.KalahComparator();
		Collections.sort( heuristics, comparator );
		
		for( int i = 0; i < count; i++ ) {
			// Pick heuristics from the end of the array and add them to the new array.
			group1_KalahHeuristic top = heuristics.get( heuristics.size() - i - 1 );
			top_heuristics.add( top );
		}
		
		return top_heuristics;
	}
	
	/*
	 * Plays the game (taken from TermKalahGame::Play, but with various things removed for speed.)
	 */
	private static void quickplay( MancalaPlayer player1, MancalaPlayer player2 ) throws Exception {
		MancalaPlayer player[] = new MancalaPlayer[2];
		player[0] = player1;
		player[1] = player2;
		
		MancalaGameState gs = new KalahPieGameState(4);
		int turnCount = 0;
		while (!gs.checkEndGame()) {
			turnCount++;
			
			int move = player[gs.CurrentPlayer()].getMove(gs);
			while (!gs.validMove(move)) {
				System.out.println("Invalid move!");
				System.out.println("Player " + gs.CurrentPlayer() + "'s move: ");
				move = player[gs.CurrentPlayer()].getMove(gs);
			}
			gs.play(move);
		}

		gs.computeFinalScore();
		player[0].postGameActions(gs);
		player[1].postGameActions(gs);
	}
}
